package org.infodavid.common.impl.persistence;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;

import javax.persistence.PersistenceException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.joda.time.DateTime;

import org.infodavid.common.annotations.AImplementation;
import org.infodavid.common.annotations.AImplementation.ERuntime;
import org.infodavid.common.dto.ITemplateVersionListItem;
import org.infodavid.common.ejb.TemplateVersionBean;
import org.infodavid.common.model.ITemplate;
import org.infodavid.common.model.ITemplateVersion;
import org.infodavid.common.model.criteria.ITemplateVersionCriteria;
import org.infodavid.common.model.impl.HibernateHelper;
import org.infodavid.common.persistence.INamed;
import org.infodavid.common.persistence.IQueryParameter;
import org.infodavid.common.persistence.PersistenceSession;
import org.infodavid.common.persistence.TemplateVersionDataService;
import org.infodavid.common.persistence.impl.DataServiceHelper;
import org.infodavid.common.persistence.impl.PersistenceUtil;
import org.infodavid.common.util.IVisitor;

/**
 * The Class TemplateVersionDataServiceImpl.
 */
@AImplementation(value = TemplateVersionDataService.class, runtime = ERuntime.DEFAULT)
public final class TemplateVersionDataServiceImpl extends AbstractTemplateVersionDataService {

  /** The Constant log. */
  private static final Log LOGGER = LogFactory.getLog(AbstractTemplateVersionDataService.class);

  /** The helper. */
  private final DataServiceHelper<ITemplateVersionListItem,ITemplateVersion,Long> helper;

  /**
   * The Constructor.
   */
  private TemplateVersionDataServiceImpl() {
    super();

    helper = new DataServiceHelper<ITemplateVersionListItem,ITemplateVersion,Long>(this);
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#getEntityClass()
   */
  public Class<? extends ITemplateVersion> getEntityClass() {
    return TemplateVersionBean.class;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#find(org.infodavid.common.persistence.PersistenceSession
   * , java.util.List, org.infodavid.common.model.criteria.ISearchCriteria)
   */
  @SuppressWarnings("unchecked")
  @Override
  public long find(final PersistenceSession session, final List<ITemplateVersionListItem> entities,
      final ITemplateVersionCriteria criteria) throws PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching by criteria using session = " + session + ", entities = " + entities
          + ", criteria = " + criteria);
    }

    PersistenceUtil.validate(session);
    helper.validate(entities);

    final List<IQueryParameter> params = new ArrayList<IQueryParameter>();
    final HibernateHelper hbnHelper = HibernateHelper.getInstance();
    final Criteria hbnCriteria = hbnHelper.createCriteria(session, TemplateVersionBean.class);
    final Criteria hbnCountCriteria = hbnHelper.createCriteria(session, TemplateVersionBean.class);

    if (criteria == null) {
      hbnCriteria.add(Restrictions.eq(ITemplateVersion.ARCHIVED, Boolean.FALSE));
      hbnCountCriteria.add(Restrictions.eq(ITemplateVersion.ARCHIVED, Boolean.FALSE));
    }
    else {
      final Boolean archived = criteria.isArchived();
      final Integer revision = criteria.getRevision();
      final Long templateKey = criteria.getTemplateKey();
      final String name = criteria.getNamePattern();

      if (!(revision == null || revision.intValue() <= 0)) {
        hbnCriteria.add(Restrictions.eq(ITemplateVersion.REVISION, revision));
        hbnCountCriteria.add(Restrictions.eq(ITemplateVersion.REVISION, revision));
      }

      if (!(templateKey == null || templateKey.longValue() < 0)) {
        if (StringUtils.isNotEmpty(name)) {
          hbnCriteria.createAlias(ITemplate.TEMPLATE, "tpl")
              .add(Restrictions.like("tpl." + ITemplate.NAME, name))
              .add(Restrictions.eq("tpl." + ITemplate.KEY, templateKey));
          hbnCountCriteria.createAlias(ITemplate.TEMPLATE, "tpl")
              .add(Restrictions.like("tpl." + ITemplate.NAME, name))
              .add(Restrictions.eq("tpl." + ITemplate.KEY, templateKey));
        }
        else {
          hbnCriteria.createAlias(ITemplate.TEMPLATE, "tpl").add(
              Restrictions.eq("tpl." + ITemplate.KEY, templateKey));
          hbnCountCriteria.createAlias(ITemplate.TEMPLATE, "tpl").add(
              Restrictions.eq("tpl." + ITemplate.KEY, templateKey));
        }
      }
      else if (StringUtils.isNotEmpty(name)) {
        hbnCriteria.createAlias(ITemplate.TEMPLATE, "tpl").add(
            Restrictions.like("tpl." + ITemplate.NAME, name));
        hbnCountCriteria.createAlias(ITemplate.TEMPLATE, "tpl").add(
            Restrictions.like("tpl." + ITemplate.NAME, name));
      }

      if (archived == null) {
        hbnCriteria.add(Restrictions.eq(ITemplateVersion.ARCHIVED, Boolean.FALSE));
        hbnCountCriteria.add(Restrictions.eq(ITemplateVersion.ARCHIVED, Boolean.FALSE));
      }
      else {
        hbnCriteria.add(Restrictions.eq(ITemplateVersion.ARCHIVED, archived));
        hbnCountCriteria.add(Restrictions.eq(ITemplateVersion.ARCHIVED, archived));
      }
    }

    PersistenceUtil.applyCriteria(params, criteria);
    hbnHelper.applyParameters(params, hbnCriteria);

    try {
      hbnCountCriteria.setProjection(Projections.count(ITemplateVersion.KEY));
      session.flush();

      final long count = ((Number)hbnCountCriteria.uniqueResult()).longValue();

      if (count > 0) {
        entities.addAll(hbnCriteria.list());
      }

      return count;
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.TemplateVersionDataService#findLastVersion(org.infodavid.common
   * .persistence.PersistenceSession, java.lang.Long)
   */
  @Override
  public ITemplateVersion findLastVersion(final PersistenceSession session, final Long templateKey)
      throws PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching last version using session = " + session + ", templateKey = "
          + templateKey);
    }

    if (templateKey == null) {
      throw new IllegalArgumentException(ITemplate.KEY);
    }

    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil.borrowQueryParameters(1);
    ITemplateVersion result;

    params.get(0).setName(ITemplate.ID).setValue(templateKey);

    try {
      session.flush(); // use of native query

      result =
          (ITemplateVersion)session.executeQuery(TemplateVersionBean.class, QUERY_FIND_LAST,
              params, true);
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }

    return result;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.TemplateVersionDataService#findLastVersionByName(org.infodavid
   * .common.persistence.PersistenceSession, java.lang.String, java.util.Locale)
   */
  @Override
  public ITemplateVersion findLastVersionByName(final PersistenceSession session,
      final String value, final Locale language) throws PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching last version using session = " + session + ", value = " + value
          + ", language = " + language);
    }

    if (StringUtils.isEmpty(value)) {
      throw new IllegalArgumentException(ITemplateVersion.NAME);
    }

    if (language == null) {
      throw new IllegalArgumentException(ITemplateVersion.LANGUAGE);
    }

    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil.borrowQueryParameters(2);
    ITemplateVersion result;

    params.get(0).setName(INamed.NAME).setValue(value);
    params.get(1).setName(ITemplate.LANGUAGE).setValue(language.getLanguage());

    try {
      session.flush(); // use of native query

      result =
          (ITemplateVersion)session.executeQuery(TemplateVersionBean.class,
              QUERY_FIND_LAST_BY_NAME, params, true);
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.TemplateVersionDataService#findLastRevision(org.infodavid.common
   * .persistence.PersistenceSession, java.lang.Long)
   */
  @Override
  public Integer findLastRevision(final PersistenceSession session, final Long templateKey)
      throws PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching last revision using session = " + session + ", templateKey = "
          + templateKey);
    }

    if (templateKey == null) {
      throw new IllegalArgumentException(ITemplate.KEY);
    }

    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil.borrowQueryParameters(1);
    Integer result;

    params.get(0).setName(ITemplate.ID).setValue(templateKey);

    try {
      session.flush(); // use of native query

      result = (Integer)session.executeQuery(Integer.class, QUERY_FIND_LAST_REVISION, params, true);
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#clear(org.infodavid.common.persistence.PersistenceSession
   * , java.util.List)
   */
  public void clear(final PersistenceSession session, final List<IQueryParameter> parameters)
      throws PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Removing entities using session = " + session + ", parameters = " + parameters);
    }

    session.removeEntities(session.findEntities(ITemplateVersion.class, QUERY_FIND, parameters),
        null);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#count(org.infodavid.common.persistence.PersistenceSession
   * , java.util.List)
   */
  public long count(final PersistenceSession session, final List<IQueryParameter> parameters)
      throws PersistenceException {
    return helper.count(session, parameters, QUERY_FIND_COUNT);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#find(org.infodavid.common.persistence.PersistenceSession
   * , java.util.List, java.util.List)
   */
  public long find(final PersistenceSession session, final List<ITemplateVersionListItem> entities,
      final List<IQueryParameter> parameters) throws PersistenceException {
    return helper.findListItems(session, entities, parameters, QUERY_FIND, QUERY_FIND_COUNT);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#findByCreationDate(org.infodavid.common.persistence
   * .PersistenceSession, org.joda.time.DateTime, org.joda.time.DateTime, java.util.List,
   * java.util.List)
   */
  public long findByCreationDate(final PersistenceSession session, final DateTime date1,
      final DateTime date2, final List<ITemplateVersionListItem> entities,
      final List<IQueryParameter> parameters) throws PersistenceException {
    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil.initializeQueryParameters(parameters);

    try {
      PersistenceUtil.applySorting(params, ITemplateVersion.CREATION_DATE);

      return helper.findListItemsByDate(session, date1, date2, entities, params,
          QUERY_FIND_BY_CREATION_DATE, QUERY_FIND_BY_CREATION_DATE_COUNT);
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#findByModificationDate(org.infodavid.common.persistence
   * .PersistenceSession, org.joda.time.DateTime, org.joda.time.DateTime, java.util.List,
   * java.util.List)
   */
  public long findByModificationDate(final PersistenceSession session, final DateTime date1,
      final DateTime date2, final List<ITemplateVersionListItem> entities,
      final List<IQueryParameter> parameters) throws PersistenceException {
    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil.initializeQueryParameters(parameters);

    try {
      PersistenceUtil.applySorting(params, ITemplateVersion.MODIFICATION_DATE);

      return helper.findListItemsByDate(session, date1, date2, entities, params,
          QUERY_FIND_BY_MODIFICATION_DATE, QUERY_FIND_BY_MODIFICATION_DATE_COUNT);
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /**
   * Increment revision.
   * @param session the session
   * @param value the value
   */
  protected void incrementRevision(final PersistenceSession session, final ITemplateVersion value) {
    final Integer lastRevision = findLastRevision(session, value.getTemplate().getKey());

    value.setRevision(lastRevision == null ? 1 : lastRevision.intValue() + 1);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#insert(org.infodavid.common.persistence.
   * PersistenceSession, java.util.Collection)
   */
  public void insert(final PersistenceSession session, final Collection<ITemplateVersion> collection)
      throws PersistenceException {
    final IVisitor<ITemplateVersion,Object> visitor = new IVisitor<ITemplateVersion,Object>() {
      @SuppressWarnings({
          "unchecked", "rawtypes"
      })
      public IResult visit(final ITemplateVersion data) throws Exception {
        incrementRevision(session, data);

        return IVisitor.SUCCESS;
      }

      public void initialize() throws Exception {}
    };

    session.insertEntities(collection, null, visitor);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#insert(org.infodavid.common.persistence.
   * PersistenceSession, org.infodavid.common.model.IDataObject)
   */
  public void insert(final PersistenceSession session, final ITemplateVersion value)
      throws PersistenceException {
    incrementRevision(session, value);
    session.insertEntity(value, null);
  }
}
